﻿/*
 * Mensages
 * Sucess
 *  
 * 100 = OK
 *  
 * Error
 * 
 * 201 - Application error
 * 202 - Invalid parameters
 * 203 - Method not found
 * 204 - Service not exist or not registered
 * 205 - Invalid method call 
 * 206 - Class or Namespace not found
 * 207 - Instance not exist or expired
 * 208 - This class constructor need parameters
 */

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;
using System.Net;
using System.IO;
using System.Threading;
using System.Diagnostics;
using NOS.Security;
using NOS.Configuration.Services;

namespace NOS.Core
{
    public class NOSCore
    {
        #region . Variables .

        public static SafetyHashTable AssembliesCache = null;
        private static SafetyHashTable Instances = null;

        #endregion

        #region . Public Functions .

        public NOSCore()
        {
            if (AssembliesCache == null)
            {
                AssembliesCache = new SafetyHashTable();
                LoadAssembliesCache();
            }
            if (Instances == null)
            {
                Instances = new SafetyHashTable();
            }
        }

        public string Process(string AbsolutPath, NameValueCollection Arguments)
        {
            string Command = AbsolutPath;
            string Service = "";
            string NameSpace = "";
            string ClassName = "";
            string FunctionName = "";           

            if (Command != "")
            {
                try
                {
                    if (Command.Split('/').GetValue(1).ToString() == "admin")
                    {
                        Trace.WriteLine("Administration Page");
                        return AdminRequest(Command, Arguments);
                    }
                    else
                    {

                        try
                        {
                            Service = Command.Split('/').GetValue(1).ToString().Replace("/", "");
                            Command = Command.Split('/').GetValue(2).ToString().Replace("/", "");
                            NameSpace = Command.Split('.').GetValue(0).ToString();
                            ClassName = Command.Split('.').GetValue(1).ToString();
                            FunctionName = Command.Split('.').GetValue(2).ToString();
                        }
                        catch
                        {
                            return string.Format(RESPONSE, "205", "Invalid Method Called", "", "");
                        }
                    }
                }
                catch
                {
                    return ERROR404;
                }
            }

            Trace.WriteLine("Call Service: " + Service + "\nNameSpace...: " + NameSpace +
                            "\nClasse......: " + ClassName + "\nFunção......: " + FunctionName);

            
            string SID = "";
            bool Keepinstance = false;

            if (Arguments["SID"] != null)
            {
                SID = Arguments["SID"];
                Arguments.Remove("SID");
            }

            if (Arguments["keepinstance"] != null)
            {
                Keepinstance = Boolean.Parse(Arguments["keepinstance"]);
                Arguments.Remove("keepinstance");
            }

            return CallAssembly(Service, NameSpace, ClassName, FunctionName, Arguments, SID, Keepinstance);
        }

        public string LoadAssembliesCache()
        {           
            DataObjectCollection objCollection = DataObjectCollection.LoadFromFile(AppDomain.CurrentDomain.BaseDirectory+"services.xml");
            
            bool AssemblyNotLoaded = false;

            AssembliesCache.GetExclusive();

            try
            {

                AssembliesCache.HashTable.Clear();

                foreach (DataObject Obj in objCollection)
                {
                    if (File.Exists(Obj.Assembly))
                    {
                        AssembliesCache.HashTable.Add(Obj.ServiceName, new AssemblyCache(Obj.ServiceName, Obj.Assembly, null));
                    }
                    else
                    {
                        AssemblyNotLoaded = true;
                    }
                }
            }
            finally
            {
                AssembliesCache.ReleaseExclusive();
            }
            
            return string.Format("Assemblies loaded sucessfully {0}",AssemblyNotLoaded?"[WARNING] Some assemblies not found":"");
        }

        #endregion

        #region . Private Functions .

        private string CallAssembly(string Service, string NameSpace, string ClassName, string FunctionName, NameValueCollection Arguments, string SID, bool Keepinstance)
        {
            #region . Variables .

            Assembly AssembLoaded = null;
            Object Obj = null;
            MethodInfo Minfo = null;
            Type Tipo = null;
            string StatusCode = "";
            string StatusMsg = "";
            string Retorno = "";
            string NewSID = "";

            #endregion

            if (SID == "")
            {
                AssembliesCache.GetShared();
                AssemblyCache DataTmp = (AssemblyCache)AssembliesCache.HashTable[Service];
                try
                {
                    if (DataTmp != null)
                    {
                        if (DataTmp.LoadedAssembly != null)
                        {
                            AssembLoaded = DataTmp.LoadedAssembly;
                        }
                        else
                        {
                            string AssemblyFile = DataTmp.AssemblyPath;
                            AssembLoaded = Assembly.LoadFrom(AssemblyFile);
                            DataTmp.LoadedAssembly = AssembLoaded;
                        }
                        Tipo = AssembLoaded.GetType(NameSpace + "." + ClassName, false);
                    }
                    else
                    {
                        return string.Format(RESPONSE, "204", "Service not exist or not registered", "", "");
                    }
                }
                finally
                {
                    AssembliesCache.ReleaseShared();
                }
            }
            else if (SID != "" && Instances.HashTable[SID] != null)
            {
                Obj = Instances.HashTable[SID];
                Tipo = Obj.GetType();
            }
            else
            {
                return string.Format(RESPONSE, "207", "Instance not exist or expired", "", "");
            }         

            if (FunctionName == "" || FunctionName == "Create")
            {
                Instances.GetExclusive();
                try
                {
                    NewSID = Guid.NewGuid().ToString();
                    ConstructorInfo Construct = null;
                    foreach(ConstructorInfo CInfo in Tipo.GetConstructors())
                    {
                        if (CInfo.GetParameters().Length == Arguments.Count)
                        {
                            Construct = CInfo;
                            break;
                        }
                    }
                    if (Construct != null)
                    {

                        object[] Parans = null;

                        if (!TreatParameters(Arguments, Construct.GetParameters(), ref Parans))
                        {
                            return string.Format(RESPONSE, "202", "Invalid parameters", "", "");
                        }

                        Obj = Construct.Invoke(Parans);
                        Instances.HashTable.Add(NewSID, Obj);
                        return string.Format(RESPONSE, "101", "Instance Created", "<SID>" + NewSID + "</SID>", "");
                    }
                    else
                    {
                        return string.Format(RESPONSE, "208", "This class constructor need parameters", "", "");
                    }
                }
                finally
                {
                    Instances.ReleaseExclusive();
                }
            }
            else
            {
                if (Tipo != null)
                {
                    try
                    {
                        if (Obj == null)
                        {
                            Obj = Activator.CreateInstance(Tipo);
                            if (Keepinstance)
                            {
                                Instances.GetExclusive();
                                try
                                {
                                    NewSID = Guid.NewGuid().ToString();
                                    Instances.HashTable.Add(NewSID, Obj);
                                }
                                finally
                                {
                                    Instances.ReleaseExclusive();
                                }
                            }
                        }
                    }
                    catch
                    {
                        return string.Format(RESPONSE, "208", "This class constructor need parameters", "", "");
                    }
                }
                else
                {
                    StatusCode = "206";
                    StatusMsg = "Class of namespace not found";
                }

                Minfo = Tipo.GetMethod(FunctionName);
                if (Minfo == null)
                {
                    return string.Format(RESPONSE, "203", "Method not Found", "", "");
                }

                object[] Param = null;
                if (!TreatParameters(Arguments, Minfo.GetParameters(), ref Param))
                {
                    return string.Format(RESPONSE, "202", "Invalid parameters", "", "");
                }

                try
                {
                    try
                    {
                        Retorno = Minfo.Invoke(Obj, Param).ToString();
                    }
                    catch
                    {
                        try
                        {
                            Minfo.Invoke(Obj, Param);
                        }
                        catch
                        {
                            throw;
                        }
                    }

                    StatusCode = "100";
                    StatusMsg = "OK";
                    if (Arguments.Count > Minfo.GetParameters().Length)
                    {
                        StatusMsg += " [WARNING] Passed more parameters that the function need";
                    }
                }
                catch (Exception e)
                {
                    StatusCode = "201";
                    StatusMsg = e.Message;
                }
            }
      
            return string.Format(RESPONSE, StatusCode, StatusMsg, NewSID != ""?"<SID>"+NewSID+"</SID>":"", Retorno != ""?"<Body>"+Retorno+"</Body>":"");
        }

        private bool TreatParameters(NameValueCollection Arguments, ParameterInfo[] FunctionParameters, ref object[] Parans)
        {
            ParameterInfo[] Parameters = FunctionParameters;
            Parans = new object[Parameters.Length];
            for (int i = 0; i < Parameters.Length; i++)
            {
                if (Arguments[Parameters[i].Name] == null)
                {
                    return false;
                }
                else if (Parameters[i].ParameterType == typeof(Int16) ||
                         Parameters[i].ParameterType == typeof(Int32) ||
                         Parameters[i].ParameterType == typeof(Int64))
                {
                    Parans[i] = Int32.Parse(Arguments[Parameters[i].Name]);
                }
                else if (Parameters[i].ParameterType == typeof(Double))
                {
                    Parans[i] = Double.Parse(Arguments[Parameters[i].Name]);
                }
                else if (Parameters[i].ParameterType == typeof(Boolean))
                {
                    Parans[i] = Boolean.Parse(Arguments[Parameters[i].Name]);
                }
                else
                {
                    Parans[i] = Arguments[Parameters[i].Name].ToString();
                }
            }
            return true;
        }

        private string AdminRequest(string Command, NameValueCollection Arguments)
        {
            string Response = "";

            if (Command == "/admin/")
            {
                Response = ADMINPAGE;
            }
            else if (Command == "/admin/reloadassemblies")
            {
                Response = ADMINRELOADWAIT; ;
            }
            else if (Command == "/admin/reloadassembliesexecute")
            {
                NOSCore Nos = new NOSCore();
                Response = string.Format(ADMINRELOADRESPONSE,Nos.LoadAssembliesCache());
            }
            else if (Command == "/admin/registredservices")
            {
                NOSCore.AssembliesCache.GetShared();

                string TableServices = "";

                TableServices = "<Table border=\"1\" cellpadding=\"1\" cellspacing=\"1\">";
                TableServices += "<tr bgcolor=\"#DFF3B1\">";
                TableServices += "<td width=\"200px\"><b>Service</b></td><td width=\"253px\"><b>Assembly</b></td>";
                TableServices += "</tr>";
                foreach (object Item in NOSCore.AssembliesCache.HashTable.Values)
                {
                    TableServices += "<tr>";
                    TableServices += "<td><a href=\"assemblydetail?servicename="+((AssemblyCache)Item).ServiceName+"&assembly=" + ((AssemblyCache)Item).AssemblyPath + "\">" + ((AssemblyCache)Item).ServiceName + "</a></td><td>" + ((AssemblyCache)Item).AssemblyPath + "</td>";
                    TableServices += "</tr>";
                }
                TableServices += "</Table>";

                NOSCore.AssembliesCache.ReleaseShared();

                Response = string.Format(ADMINREGISTREDSERVICES, TableServices);

            }
            else if (Command.Split('/').GetValue(2).ToString().Split('=').GetValue(0).ToString() == "assemblydetail")
            {
                string AssembPath = Arguments["assembly"].ToString();
                string AssembName = Arguments["assembly"].ToString().Split('\\').GetValue(7).ToString();
                string ServiceName = Arguments["servicename"].ToString();
                string Body = "";
                string LastNameSpace = "";

                Assembly Ass = Assembly.LoadFrom(AssembPath);

                foreach (Type CType in Ass.GetTypes())
                {
                    if (LastNameSpace != CType.Namespace )
                        Body += "namespace " + CType.Namespace + " {<br><br>";

                    Body += "&nbsp;&nbsp;&nbsp;&nbsp;public class " + CType.Name + " { <br><br>";
                    foreach (MethodInfo mi in CType.GetMethods())
                    {
                        if (mi.DeclaringType == CType && mi.IsPublic)
                        {
                            string ReturnType = mi.ReturnType.Name;
                            string FunctioName = mi.Name;
                            string Parameters = "";
                            foreach (ParameterInfo pinfo in mi.GetParameters())
                            {
                                if (Parameters != "")
                                {
                                    Parameters += ", ";
                                }
                                Parameters += pinfo.ParameterType.Name + " " + pinfo.Name;
                            }

                            Body += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public "+ReturnType+" "+FunctioName+"("+Parameters+")<br>";
                        }
                    }
                    Body += "&nbsp;&nbsp;&nbsp;&nbsp;} <br><br>";
                    LastNameSpace = CType.Namespace;
                    if (LastNameSpace != CType.Namespace)
                    {
                        Body += "}";
                    }
                    Body += "<br><br>";
                }
                Body += "}<br><br>";

                Response = string.Format(ADMINASSEMBLYDET,ServiceName,AssembName, Body);
            }
            else
            {
                Response = ERROR404;
            }

            return Response;
        }

        #endregion

        #region . Contants .
     
        private const string ERROR_RESPONSE = "<?xml version=\"1.0\" encoding=\"utf-8\"?><NOS><ERRORCODE>{0}</ERRORCODE><ERROR>{1}</ERROR></NOS>";
        private const string RESPONSE = "<?xml version=\"1.0\" encoding=\"utf-8\"?><NOS><Header><StatusCode>{0}</StatusCode><Message>{1}</Message>{2}</Header>{3}</NOS>";

        private const string ADMINPAGE = "<HTML><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>" +
                                         "<HEAD><TITLE>NOS - Administration</TITLE></HEAD>" +
                                         "<BODY style=\"font-family: Verdana\">" +
                                         "<h3><U>NOS - Administration</U></h3>" +
                                         "<div id=\"Menu\"><ul>" +
                                         "<li><a href=\"reloadassemblies\">Reload Assemblies</a></li>" +
                                         "<li><a href=\"registredservices\">Registred Services</a></li>" +
                                         "</ul></div></BODY></HTML>";

        private const string ADMINRELOADRESPONSE = "<HTML><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>" +
                                                   "<HEAD><TITLE>NOS - Administration</TITLE></HEAD>" +
                                                   "<BODY style=\"font-family: Verdana\">" +
                                                   "<h3>{0}</h3><br>" +
                                                   "<a href=\"/admin/\">Home</a></BODY></HTML>";

        private const string ADMINRELOADWAIT = "<HTML><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>" +
                                               "<meta http-equiv=\"refresh\" content=\"0; url=reloadassembliesexecute\">" +
                                               "<HEAD><TITLE>NOS - Administration</TITLE></HEAD>" +
                                               "<BODY style=\"font-family: Verdana\"><h3>Wait, reloading assemblies</h3><br>" +
                                               "This operation can take some minutes</BODY></HTML>";

        private const string ADMINREGISTREDSERVICES = "<HTML><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>" +
                                                      "<HEAD><TITLE>NOS - Administration</TITLE></HEAD>" +
                                                      "<BODY style=\"font-family: Verdana\">" +
                                                      "<h3><U>Registred Services</U></h3><br>" +
                                                      "{0}<br><br><a href=\"/admin/\">Home</a></BODY></HTML>";

        private const string ERROR404 = "<HTML><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>" +
                                        "<HEAD><TITLE>404 - Page not found</TITLE></HEAD>" +
                                        "<BODY style=\"font-family: Verdana\"><h3>Page not found</h3></BODY></HTML>";

        private const string ADMINASSEMBLYDET = "<HTML><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>" +
                                                "<HEAD><TITLE>NOS - Administration</TITLE></HEAD>" +
                                                "<BODY style=\"font-family: Verdana\"><h3><u>Assembly Detail</u></h3><br>Service Name: {0}<br>Assembly Name: {1}<br><br>{2}<br><br><li><a href=\"/admin/\">Home</a></li><li><a href=\"registredservices\">Registred Services</a></li></BODY></HTML>";

        #endregion

    }
}